#pragma once

#include <iostream>
#include <cmath>

namespace cge {
	template< class T >
	class base_vec2 {
	public:
		T x, y;

		base_vec2(): x(0.0), y(0.0) {}
		base_vec2( T initialX, T initialY ): x(initialX), y(initialY) {}
		base_vec2( const base_vec2& r ): x(r.x), y(r.y) {}
		base_vec2( T s ): x(s), y(s) {}

		void set( T newX, T newY ) { x=newX; y=newY; }

		base_vec2& operator= ( const base_vec2& r ) { set(r.x,r.y); return *this; }
		
		base_vec2& operator+=( const base_vec2& r ) { x+=r.x; y+=r.y; return *this; }
		base_vec2& operator-=( const base_vec2& r ) { x-=r.x; y-=r.y; return *this; }
		base_vec2& operator*=( T s ) { x*=s; y*=s; return *this; }
		base_vec2& operator/=( T s ) { x/=s; y/=s; return *this; }
		
		base_vec2  operator+( const base_vec2& r ) const { return base_vec2(x+r.x,y+r.y); }
		base_vec2  operator-( const base_vec2& r ) const { return base_vec2(x-r.x,y-r.y); }
		base_vec2  operator*( T s ) const { return base_vec2(x*s,y*s); }
		base_vec2  operator/( T s ) const { return base_vec2(x/s,y/s); }
		base_vec2  operator-()      const { return base_vec2(-x,-y); }
		
		friend base_vec2 operator*(T s, const base_vec2& v) { return v*s; }
		
		base_vec2 unit()                         const { Vector2 v(*this); v.normalize(); return v; }
		base_vec2 modulate( const base_vec2 &r ) const { return base_vec2(x*v.x,y*v.y); }
		
		T dot( const base_vec2& r ) const { return x*r.x + y*r.y; }
		T norm()                    const { return sqrt(dot(*this)); }
		T normSquared()             const { return dot(*this); }
		

		void      normalize() { T n = norm(); if(n!=0.0) (*this)/=n; }

		operator T*() { return reinterpret_cast<T*>(this); }
	};

	template< class T >
	class base_vec3 {
	public:
		T x, y, z;

		base_vec3(): x(0.0), y(0.0), z(0.0) {}
		base_vec3( T *arr ): x(arr[0]), y(arr[1]), z(arr[2]) {}
		base_vec3( T initialX, T initialY, T initialZ ): x(initialX), y(initialY), z(initialZ) {}
		base_vec3( const base_vec3& r ): x(r.x), y(r.y), z(r.z) {}
		base_vec3( T s ): x(s), y(s), z(s) {}

		void set( T *arr ) { x=arr[0]; y=arr[1]; z=arr[2]; }
		void set( T newX, T newY, T newZ ) { x=newX; y=newY; z=newZ; }

		void copyTo( T* arr ) { arr[0]=x; arr[1]=y; arr[2]=z; }

		base_vec3& operator= ( const base_vec3& r ) { set(r.x,r.y,r.z); return *this; }
		
		base_vec3& operator+=( const base_vec3& r ) { x+=r.x; y+=r.y; z+=r.z; return *this; }
		base_vec3& operator-=( const base_vec3& r ) { x-=r.x; y-=r.y; z-=r.z; return *this; }
		base_vec3& operator*=( T s ) { x*=s; y*=s; z*=s; return *this; }
		base_vec3& operator/=( T s ) { x/=s; y/=s; z/=s; return *this; }
		
		base_vec3  operator+( const base_vec3& r ) const { return base_vec3(x+r.x,y+r.y,z+r.z); }
		base_vec3  operator-( const base_vec3& r ) const { return base_vec3(x-r.x,y-r.y,z-r.z); }
		base_vec3  operator*( T s ) const { return base_vec3(x*s,y*s,z*s); }
		base_vec3  operator/( T s ) const { return base_vec3(x/s,y/s,z/s); }
		base_vec3  operator-()      const { return base_vec3(-x,-y,-z); }

		bool       operator>=( const base_vec3& r ) const { return x>=r.x && y>=r.y && z>=r.z; }
		bool       operator<=( const base_vec3& r ) const { return x<=r.x && y<=r.y && z<=r.z; }
		
		friend base_vec3 operator*(T s, const base_vec3& v) { return v*s; }
		
		base_vec3 unit()                      const { Vector2 v(*this); v.normalize(); return v; }
		base_vec3 cross( const base_vec3& r ) const { return base_vec3(y*r.z-z*r.y,z*r.x-x*r.z,x*r.y-y*r.x); }
		T         dot( const base_vec3& r )   const { return x*r.x + y*r.y + z*r.z; }
		T         norm()                      const { return sqrt(dot(*this)); }
		T         normSquared()               const { return dot(*this); }
		
		void      normalize() { T n = norm(); if(n) (*this)/=n; }
		
		
		friend std::ostream& operator<<( std::ostream &os, const base_vec3 &v ) {
			os << '(' <<  v.x << ", " << v.y << ", " << v.z << ')';
			return os;
		}

		operator T*()               { return reinterpret_cast<T*>(this); }
		operator (const T*)() const { return reinterpret_cast<const T*>(this); }
	};

	template< class T >
	class base_vec4 {
	public:
		T w, x, y, z;

		base_vec4(): w(0.0), x(0.0), y(0.0), z(0.0) {}
		base_vec4( T initialW, T initialX, T initialY, T initialZ): w(initialW), x(initialX), y(initialY), z(initialZ) {}
		base_vec4( const base_vec4& r ): w(r.w), x(r.x), y(r.y), z(r.z) {}
		explicit base_vec4( T s ): w(s), x(s), y(s), z(s) {}

		void set( T newW, T newX, T newY, T newZ ) { w=newW; x=newX; y=newY; z=newZ; }

		base_vec4& operator= ( const base_vec4& r ) { set(r.w,r.x,r.y,r.z); return *this; }
		
		base_vec4& operator+=( const base_vec4& r ) { x+=r.x; y+=r.y; z+=r.z; w+=r.w; return *this; }
		base_vec4& operator-=( const base_vec4& r ) { x-=r.x; y-=r.y; z-=r.z; w-=r.w; return *this; }
		base_vec4& operator*=( T s ) { x*=s; y*=s; z*=s; w*=s; return *this; }
		base_vec4& operator/=( T s ) { x/=s; y/=s; z/=s; w/=s; return *this; }
		
		base_vec4  operator+( const base_vec4& r ) const { return base_vec4(w+r.w,x+r.x,y+r.y,z+r.z); }
		base_vec4  operator-( const base_vec4& r ) const { return base_vec4(w-r.w,x-r.x,y-r.y,z-r.z); }
		base_vec4  operator*( T s ) const { return base_vec4(w*s,x*s,y*s,z*s); }
		base_vec4  operator/( T s ) const { return base_vec4(w/s,x/s,y/s,z/s); }
		base_vec4  operator-()      const { return base_vec4(-w,-x,-y,-z); }
		
		friend base_vec4 operator*(T s, const base_vec4& v) { return v*s; }
		
		base_vec4 modulate( const base_vec4& r) const { return base_vec4(w*r.w,x*r.x,y*r.y,z*r.z); }
		base_vec4 unit()                        const { Vector2 v(*this); v.normalize(); return v; }
		T         dot( const base_vec4& r )     const { return x*r.x + y*r.y + z*r.z + w*r.w; }
		T         norm()                        const { return sqrt(dot(*this)); }
		T         normSquared()                 const { return dot(*this); }
		
		
		void      normalize() { T n = norm(); if(n) (*this)/=n; }

		friend std::ostream& operator<<( std::ostream &os, const base_vec4 &v ) {
			os << '(' << v.w << ", " << v.x << ", " << v.y << ", " << v.z << ')';
			return os;
		}

		operator T*() { return reinterpret_cast<T*>(this); }
	};

	typedef base_vec2<double> vec2d;
	typedef base_vec3<double> vec3d;
	typedef base_vec4<double> vec4d;
	typedef base_vec2<float>  vec2f;
	typedef base_vec3<float>  vec3f;
	typedef base_vec4<float>  vec4f;

	typedef float             Scalar;
	typedef base_vec2<Scalar> vec2;
	typedef base_vec3<Scalar> vec3;
	typedef base_vec4<Scalar> vec4;


	template< class T >
	T linearInterpolate( const T& a, const T& b, double delta ) {
		return a*(1.0-delta) + b*delta;
	}
}
